package ace.module.captcha.sms.core.impl.service.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.exception.BusinessException;
import ace.cmp.core.model.R;
import ace.cmp.core.utils.SystemClockUtils;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.id.generator.api.LongIdGenerator;
import ace.cmp.spring.util.core.LogUtils;
import ace.module.captcha.sms.api.constant.CaptchaSmsErrorCodeConstant;
import ace.module.captcha.sms.core.impl.dao.entity.CaptchaSmsApplication;
import ace.module.captcha.sms.core.impl.dao.entity.CaptchaSmsApplicationExtend;
import ace.module.captcha.sms.core.impl.dao.entity.CaptchaSmsItem;
import ace.module.captcha.sms.core.impl.dao.entity.CaptchaSmsItemConfig;
import ace.module.captcha.sms.core.impl.manager.CaptchaSmsApplicationExtendManager;
import ace.module.captcha.sms.core.impl.manager.CaptchaSmsApplicationManager;
import ace.module.captcha.sms.core.impl.manager.CaptchaSmsItemConfigManager;
import ace.module.captcha.sms.core.impl.manager.CaptchaSmsItemManager;
import ace.module.captcha.sms.core.impl.service.CaptchaSmsService;
import ace.module.common.api.constant.CommonConstants;
import ace.module.sms.api.SmsApi;
import java.time.Instant;
import java.util.function.Function;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

/**
 * @author Caspar
 * @create 2020/11/5
 */
@Slf4j
@AllArgsConstructor
@Component
public class CaptchaSmsServiceImpl implements CaptchaSmsService {

  private static final String MSG_CODE_ERROR = "验证码错误";
  private final CaptchaSmsApplicationManager captchaSmsApplicationManager;
  private final CaptchaSmsApplicationExtendManager captchaSmsApplicationExtendManager;
  private final CaptchaSmsItemConfigManager captchaSmsItemConfigManager;
  private final CaptchaSmsItemManager captchaSmsItemManager;
  private final MessageSourceAssertService assertService;
  private final LongIdGenerator idGenerator;
  private final SmsApi smsApi;

  @Override
  public R<Void> sendNoCode(Long captchaSmsAppId, String phoneNumber) {
    return LogUtils.log(
        "send_exception",
        () -> {
          R<String> sendResult = this.send(captchaSmsAppId, phoneNumber, this::generateCode);
          return R.of(sendResult, null);
        });
  }

  @Override
  public R<String> send(Long captchaSmsAppId, String phoneNumber) {
    return LogUtils.log(
        "send_exception",
        () -> {
          return this.send(captchaSmsAppId, phoneNumber, this::generateCode);
        });
  }

  @Override
  public R<String> send(Long captchaSmsAppId, String phoneNumber, String code) {
    return LogUtils.log(
        "send_exception",
        () -> {
          return this.send(captchaSmsAppId, phoneNumber, (itemConfig) -> code);
        });
  }

  @Override
  public R<Void> verify(Long captchaSmsAppId, String phoneNumber, String code) {
    return LogUtils.log(
        "verify_exception",
        () -> {
          CaptchaSmsApplication captchaSmsApplication =
              captchaSmsApplicationManager.getAndCheckAllById(captchaSmsAppId);

          return this.verify(captchaSmsApplication, phoneNumber, code);
        });
  }

  @Override
  public R<Void> verify(String captchaSmsAppKey, String phoneNumber, String code) {
    return LogUtils.log(
        "verify_exception",
        () -> {
          CaptchaSmsApplication captchaSmsApplication =
              captchaSmsApplicationManager.getAndCheckAllByKey(captchaSmsAppKey);

          return this.verify(captchaSmsApplication, phoneNumber, code);
        });
  }

  private R<Void> verify(
      CaptchaSmsApplication captchaSmsApplication, String phoneNumber, String code) {

    CaptchaSmsItemConfig itemConfig =
        this.captchaSmsItemConfigManager.getAndCheckAllById(captchaSmsApplication.getId());

    Long captchaSmsAppId = captchaSmsApplication.getId();

    // 不启用
    if (BooleanUtils.isNotTrue(itemConfig.getEnable())) {
      return R.ok();
    }

    // 万能验证码
    if (BooleanUtils.isTrue(itemConfig.getEnableSystemCode())
        && StringUtils.isNotEmpty(itemConfig.getSystemCode())
        && StringUtils.equalsIgnoreCase(itemConfig.getSystemCode(), code)) {
      this.captchaSmsItemManager.remove(captchaSmsAppId, phoneNumber);
      return R.ok();
    }

    CaptchaSmsItem record =
        this.captchaSmsItemManager.getByCaptchaSmsAppIdAndPhone(captchaSmsAppId, phoneNumber);

    assertService.isNull(
        record,
        CaptchaSmsItem.class.getName() + ".code-is-error",
        MSG_CODE_ERROR,
        msg -> {
          throw new BusinessException(
              CaptchaSmsErrorCodeConstant.CAPTCHA_SMS_VERITY_CODE_FAILURE, msg);
        });

    // 验证次数
    if (record.getAttemptCount() >= itemConfig.getAttemptCount()) {
      this.captchaSmsItemManager.remove(captchaSmsAppId, phoneNumber);
      throw new BusinessException(
          CaptchaSmsErrorCodeConstant.CAPTCHA_SMS_VERITY_CODE_FAILURE, "验证码超过次数限制");
    }
    // 过期时间
    if (SystemClockUtils.currentTimeMillis() > record.getExpireTime()) {
      this.captchaSmsItemManager.remove(captchaSmsAppId, phoneNumber);
      throw new BusinessException(
          CaptchaSmsErrorCodeConstant.CAPTCHA_SMS_VERITY_CODE_FAILURE, "验证码已过期");
    }
    // 更新
    if (!StringUtils.equalsIgnoreCase(record.getCode(), code)) {
      record.setAttemptCount(record.getAttemptCount() + 1);
      record.setUpdateTime(SystemClockUtils.currentTimeMillis());
      this.captchaSmsItemManager.modify(record);
      throw new BusinessException(
          CaptchaSmsErrorCodeConstant.CAPTCHA_SMS_VERITY_CODE_FAILURE, MSG_CODE_ERROR);
    }

    this.captchaSmsItemManager.remove(captchaSmsAppId, phoneNumber);

    return R.ok();
  }

  private R<String> send(
      Long captchaSmsAppId,
      String phoneNumber,
      Function<CaptchaSmsItemConfig, String> generateCodeFn) {
    return LogUtils.log(
        "send_exception",
        () -> {
          CaptchaSmsApplication captchaSmsApplication =
              captchaSmsApplicationManager.getAndCheckAllById(captchaSmsAppId);

          CaptchaSmsApplicationExtend captchaSmsApplicationExtend =
              this.captchaSmsApplicationExtendManager.getAndCheckAllByCaptchaSmsAppId(
                  captchaSmsAppId);

          CaptchaSmsItemConfig captchaSmsItemConfig =
              this.captchaSmsItemConfigManager.getAndCheckAllById(
                  captchaSmsApplicationExtend.getCaptchaSmsItemConfigId());

          String code = generateCodeFn.apply(captchaSmsItemConfig);

          return this.send(
              captchaSmsApplication,
              captchaSmsApplicationExtend,
              captchaSmsItemConfig,
              phoneNumber,
              code);
        });
  }

  private R<String> send(
      CaptchaSmsApplication captchaSmsApplication,
      CaptchaSmsApplicationExtend captchaSmsApplicationExtend,
      CaptchaSmsItemConfig itemConfig,
      String phoneNumber,
      String code) {
    return LogUtils.log(
        "send_exception",
        () -> {
          Long captchaSmsAppId = captchaSmsApplication.getId();

          CaptchaSmsItem preRecord =
              this.captchaSmsItemManager.getByCaptchaSmsAppIdAndPhone(
                  captchaSmsApplication.getId(), phoneNumber);
          // 删除无效记录
          if (preRecord != null
              && preRecord.getExpireTime() < SystemClockUtils.currentTimeMillis()) {
            this.captchaSmsItemManager.remove(captchaSmsAppId, phoneNumber);
            preRecord = null;
          }
          // 发送间隔限制
          if (preRecord != null) {
            Instant createTime = Instant.ofEpochMilli(preRecord.getCreateTime());
            Instant sendIntervalExpireTime = createTime.plusSeconds(itemConfig.getSendIntervalInSeconds());

            assertService.isTrue(
                sendIntervalExpireTime.isAfter(Instant.now()),
                CaptchaSmsItem.class.getName() + ".send-interval-limit-error",
                "短信已发送,请稍候"
            );
          }

          Instant expireTime = Instant.now().plusSeconds(itemConfig.getExpireInSeconds());
          CaptchaSmsItem record =
              CaptchaSmsItem.builder()
                  .id(idGenerator.get())
                  .captchaSmsAppId(captchaSmsApplication.getId())
                  .code(code)
                  .attemptCount(0)
                  .phone(phoneNumber)
                  .expireTime(expireTime.toEpochMilli())
                  .deleteFlag(CoreConstant.DELETE_FLAG_NOT)
                  .createTime(SystemClockUtils.currentTimeMillis())
                  .updateTime(SystemClockUtils.currentTimeMillis())
                  .rowVersion(CommonConstants.DEFAULT_ROW_VERSION)
                  .build();

          this.captchaSmsItemManager.add(record);

          R<Void> sendSmsResult = this.smsApi.sendVerifyCode(captchaSmsApplicationExtend.getSmsAppId(), phoneNumber, code);

          if (BooleanUtils.isNotTrue(sendSmsResult.success())) {
            this.captchaSmsItemManager.remove(record.getCaptchaSmsAppId(), record.getPhone());
          }

          return R.ok(code);
        });
  }

  private String generateCode(CaptchaSmsItemConfig itemConfig) {
    assertService.isEmpty(
        itemConfig.getCodeString(),
        CaptchaSmsItemConfig.class.getName() + ".code-string-must-not-be-empty",
        "验证码字符串配置不能为空");
    assertService.isTrue(
        itemConfig.getCodeLength().intValue() < 1,
        CaptchaSmsItemConfig.class.getName() + ".code-length-greater-thant-1",
        "验证码字符串长度配置必须大于1");
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < itemConfig.getCodeLength(); i++) {
      int charIndex = RandomUtils.nextInt(0, itemConfig.getCodeLength());
      char c = itemConfig.getCodeString().charAt(charIndex);
      sb.append(c);
    }
    return sb.toString();
  }
}
